package com.nrrg.jgraphtTest;

import org.jgrapht.Graph;
import org.jgrapht.alg.interfaces.ShortestPathAlgorithm;
import org.jgrapht.alg.scoring.BetweennessCentrality;
import org.jgrapht.alg.shortestpath.DijkstraShortestPath;
import org.jgrapht.generate.GnpRandomGraphGenerator;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.graph.SimpleGraph;
import org.jgrapht.traverse.DepthFirstIterator;
import org.jgrapht.util.SupplierUtil;
import java.util.*;
import java.util.function.Supplier;

public class gTest {
    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        cascadingFailureMotterLaiModel();
//        shortestPathTest();
        long endTime = System.currentTimeMillis();
        System.out.println("Total time is:" + (float) (endTime - startTime) / 1000 + "s.");
//        stringNodeType();
    }

    private static void stringNodeType() {
        Supplier<String> vSupplier = new Supplier<>() {
            private int id = 0;

            @Override
            public String get() {
                return "v" + id++;
            }
        };

        Graph<String, DefaultEdge> erGraph = new SimpleGraph<>(vSupplier, SupplierUtil.createDefaultEdgeSupplier(), false);
        GnpRandomGraphGenerator<String, DefaultEdge> erGraphGen = new GnpRandomGraphGenerator<>(100, 0.7);
        erGraphGen.generateGraph(erGraph);

        System.out.println(erGraph);

        Iterator<String> iter = new DepthFirstIterator<>(erGraph);
        while (iter.hasNext()) {
            String vertex = iter.next();
//            Set<DefaultEdge> a = erGraph.edgesOf(vertex);
            System.out.println("Vertex " + vertex + " is connected to:" + erGraph.edgesOf(vertex).toString());

        }

        BetweennessCentrality<String, DefaultEdge> erGraphBetweennessCentrality = new BetweennessCentrality<>(erGraph, true);
        Map<String, Double> a = erGraphBetweennessCentrality.getScores();
        System.out.println(a);
    }

    public static void shortestPathTest() {
        Graph<nodeDemo, DefaultEdge> erGraph = generateERGraph();

//        GraphVertexChangeEvent<nodeDemo> gvce = new GraphVertexChangeEvent<>(new nodeDemo(1,0,0));
        DijkstraShortestPath<nodeDemo, DefaultEdge> dijkAlg = new DijkstraShortestPath<>(erGraph);

//        nodeDemo src = (nodeDemo) erGraph.vertexSet().stream().filter(n->n.getId() ==  0).findAny().get();
        nodeDemo src = (nodeDemo) erGraph.vertexSet().stream().filter(n -> n.getId() == 0);
        System.out.println(src);
        nodeDemo des = (nodeDemo) erGraph.vertexSet().stream().filter(n->n.getId() ==  10);

        ShortestPathAlgorithm.SingleSourcePaths<nodeDemo, DefaultEdge> sPaths = dijkAlg.getPaths(src);
        System.out.println(sPaths.getPath(des));
    }

    //using user-defined node type to do the load-capacity cascading failure model.
    private static void cascadingFailureMotterLaiModel() {
        Graph<nodeDemo, DefaultEdge> erGraph = generateERGraph();

        // Select the initial attack nodes.
        HashSet<nodeDemo> initalAttackVertexs = generateInitialAttackVertexs(erGraph, (float) 0.6);

        // Calculating the betweenness centrality of the Graph.
        updateCapacityAndTraffic(erGraph);

        // The cascading process
        monteCarloCascadingProcess(erGraph, initalAttackVertexs);


    }

    private static Graph<nodeDemo, DefaultEdge> generateERGraph() {
        Supplier<nodeDemo> vSupplier = new Supplier<>() {
            private int id = 0;

            @Override
            public nodeDemo get() {
                return new nodeDemo(id++, 0, 0);
            }
        };

        Graph<nodeDemo, DefaultEdge> erGraph = createERGraph(vSupplier);
        return erGraph;
    }

    private static void monteCarloCascadingProcess(Graph<nodeDemo, DefaultEdge> erGraph, HashSet<nodeDemo> initalAttackVertexs) {
        for (int n = 0; n < 1000; n++) {
            Set<nodeDemo> failVertexs = (Set<nodeDemo>) initalAttackVertexs.clone();
            Graph<nodeDemo, DefaultEdge> g = deelCloneGraph(erGraph);


            System.out.println(failVertexs.size());

            cascadingProcess(failVertexs, g);
        }
    }

    private static void cascadingProcess(Set<nodeDemo> failVertexs, Graph<nodeDemo, DefaultEdge> g) {
        int i = 1;
        while (!failVertexs.isEmpty()) {
            g.removeAllVertices(failVertexs);
            updateTraffic(g);
            failVertexs.clear();
            Iterator<nodeDemo> iter = new DepthFirstIterator<>(g);
            while (iter.hasNext()) {
                nodeDemo vertex = iter.next();
                if (vertex.overFlow()) {
                    failVertexs.addAll(Collections.singleton(vertex));
                }
            }
            System.out.println("The giant component graph size is: " + g.vertexSet().size() + " in " + i + " step.");
            i++;
        }
    }

    private static Graph<nodeDemo, DefaultEdge> deelCloneGraph(Graph<nodeDemo, DefaultEdge> erGraph) {
        // deep clone
        Graph<nodeDemo, DefaultEdge> g = new SimpleGraph<>(DefaultEdge.class);
        for (nodeDemo v : erGraph.vertexSet()) {
            g.addVertex(v);
        }
        for (DefaultEdge e : erGraph.edgeSet()) {
            g.addEdge(g.getEdgeTarget(e), g.getEdgeSource(e));
        }
        return g;
    }

    private static HashSet<nodeDemo> generateInitialAttackVertexs(Graph<nodeDemo, DefaultEdge> erGraph, float p) {
        Set<nodeDemo> vertexSet = erGraph.vertexSet();
        List<nodeDemo> vertexList = new ArrayList<>(vertexSet);
        HashSet<nodeDemo> initalAttackVertexs = new HashSet<>();
        float initalAttackProbability = p;
        int initalAttackNumber = (int) (vertexSet.size() * initalAttackProbability);
        for (int i = 0; i < initalAttackNumber; i++) {
            int temp = (int) (Math.random() * initalAttackNumber);
            initalAttackVertexs.addAll(Collections.singleton(vertexList.get(temp)));
            vertexList.set(temp, vertexList.get(initalAttackNumber));
            initalAttackNumber--;

        }
        return initalAttackVertexs;
    }

    private static Graph<nodeDemo, DefaultEdge> createERGraph(Supplier<nodeDemo> vSupplier) {
        // Generate the ER network
        Graph<nodeDemo, DefaultEdge> erGraph = new SimpleGraph<>(vSupplier, SupplierUtil.createDefaultEdgeSupplier(), false);
        GnpRandomGraphGenerator<nodeDemo, DefaultEdge> erGraphGen = new GnpRandomGraphGenerator<>(100, 0.3);
        erGraphGen.generateGraph(erGraph);
        return erGraph;
    }

    private static void printGraphCapacityAndTraffic(Graph<nodeDemo, DefaultEdge> erGraph) {
        Iterator<nodeDemo> iter;
        iter = new DepthFirstIterator<>(erGraph);
        while (iter.hasNext()) {
            nodeDemo vertex = iter.next();
            System.out.println("V" + vertex.getId() + ":" + vertex.getTraffic());
        }
    }

    private static void updateCapacityAndTraffic(Graph<nodeDemo, DefaultEdge> erGraph) {
        System.out.println("Calculating the betweenness centrality");
        BetweennessCentrality<nodeDemo, DefaultEdge> erGraphBetweennessCentrality = new BetweennessCentrality<>(erGraph, true);
        Map<nodeDemo, Double> erGraphScores = erGraphBetweennessCentrality.getScores();
        Iterator<nodeDemo> iter = new DepthFirstIterator<>(erGraph);
        while (iter.hasNext()) {
            nodeDemo vertex = iter.next();
//            System.out.println(erGraphScores.get(vertex));
            double traffic = erGraphScores.get(vertex);
            vertex.setTraffic((float) traffic);
            vertex.setCapacity((float) (traffic * (1 + 0.1)));
        }
    }

    private static void updateTraffic(Graph<nodeDemo, DefaultEdge> erGraph) {
        System.out.println("Calculating the betweenness centrality");
        BetweennessCentrality<nodeDemo, DefaultEdge> erGraphBetweennessCentrality = new BetweennessCentrality<>(erGraph, true);
        Map<nodeDemo, Double> erGraphScores = erGraphBetweennessCentrality.getScores();
        Iterator<nodeDemo> iter = new DepthFirstIterator<>(erGraph);
        while (iter.hasNext()) {
            nodeDemo vertex = iter.next();
//            System.out.println(erGraphScores.get(vertex));
            double traffic = erGraphScores.get(vertex);
            vertex.setTraffic((float) traffic);
//            vertex.setCapacity((float)(traffic * (1 + 0.1)));
        }
    }
}
